#include "fuConsoleRunner.h"

#if defined(_WIN32) || defined(WIN32)

BEGIN_NAMESPACE_CPPX

const int BUF_SIZE = 8192;

fuConsoleRunner::fuConsoleRunner(std::ostream & output,bool hide)
:
m_output(output),
m_hide(hide)
{
	m_bStopped				= false;
	m_dwSleepMilliseconds	= 100;
}

fuConsoleRunner::~fuConsoleRunner(void)
{
}

void fuConsoleRunner::Run(const std::string & exe_path,const std::string & cmd_line)
{
	PROCESS_INFORMATION	ProcessInfo;
	HANDLE				PipeReadHandle;
	HANDLE				PipeWriteHandle;

    m_output << "Running " << exe_path << std::endl;

	//	Create a pipe for the child's STDOUT.
	if( !CreatePipeLine(PipeReadHandle,PipeWriteHandle) )
		return;

	if( !ExecuteCommand(exe_path,cmd_line,PipeWriteHandle,ProcessInfo) )
		return;

	if( !ReadPipeOutput(ProcessInfo,PipeReadHandle) )
		return;

	//	Close handles.
	if( !CloseHandle(ProcessInfo.hThread) )
		ShowLastError("Error closing thread handle.");

	if( !CloseHandle(ProcessInfo.hProcess) )
		ShowLastError("Error closing process handle.");

	if( !CloseHandle(PipeReadHandle) )
		ShowLastError("Error closing pipe read handle.");

	if( !CloseHandle(PipeWriteHandle) )
		ShowLastError("Error closing pipe write handle.");
}

bool fuConsoleRunner::CreatePipeLine(HANDLE & PipeReadHandle, HANDLE & PipeWriteHandle)
{
	BOOL Success;

	SECURITY_ATTRIBUTES SecurityAttributes  = {0};
	SecurityAttributes.nLength              = sizeof(SECURITY_ATTRIBUTES);
	SecurityAttributes.bInheritHandle       = TRUE;
	SecurityAttributes.lpSecurityDescriptor = NULL;

	Success = CreatePipe(
		&PipeReadHandle,		// address of variable for read handle
		&PipeWriteHandle,		// address of variable for write handle
		&SecurityAttributes,	// pointer to security attributes
		0						// number of bytes reserved for pipe (use default size)
	);

	if ( !Success ){
		ShowLastError("Error creating pipe");
		return false;
	}

	return true;
}

bool fuConsoleRunner::ExecuteCommand(const std::string & exe_path,
                                     const std::string & cmd_line, 
                                     HANDLE PipeWriteHandle, 
                                     PROCESS_INFORMATION & ProcessInfo)
{
	BOOL			Success;
	STARTUPINFOA	StartupInfo;

	//--------------------------------------------------------------------------
	//	Zero the structures.
	//--------------------------------------------------------------------------
	ZeroMemory( &StartupInfo, sizeof( StartupInfo ));
	ZeroMemory( &ProcessInfo, sizeof( ProcessInfo ));

	//--------------------------------------------------------------------------
	//	Set up members of STARTUPINFO structure.
	//--------------------------------------------------------------------------
	StartupInfo.cb           = sizeof(STARTUPINFOA);
	StartupInfo.dwFlags      = STARTF_USESHOWWINDOW;
    if( PipeWriteHandle ) {
        StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
    }
	StartupInfo.wShowWindow  = m_hide ? SW_HIDE : SW_SHOW;
	StartupInfo.hStdOutput   = PipeWriteHandle;
	StartupInfo.hStdError    = PipeWriteHandle;

	//----------------------------------------------------------------------------
	//	Create the child process.
	//----------------------------------------------------------------------------
	Success = CreateProcessA(
		exe_path.c_str(),			// pointer to name of executable module
		LPSTR(cmd_line.c_str()),	// command line
		NULL,						// pointer to process security attributes
		NULL,						// pointer to thread security attributes (use primary thread security attributes)
		TRUE,						// inherit handles
		0,							// creation flags
		NULL,						// pointer to new environment block (use parent's)
		NULL,						// pointer to current directory name
		&StartupInfo,				// pointer to STARTUPINFO
		&ProcessInfo				// pointer to PROCESS_INFORMATION
	);

	if ( !Success ){
		ShowLastError("Error creating process");
		return false;
	}

	return true;
}

bool fuConsoleRunner::ReadPipeOutput(PROCESS_INFORMATION ProcessInfo, HANDLE PipeReadHandle)
{
	BOOL	Success;
	CHAR	PipeData[BUF_SIZE] = {0};
	DWORD	NumBytesRead;
	DWORD	TotalBytesAvailable = 0;
	DWORD	BytesLeftThisMessage = 0;

	m_stdout.clear();

	while(true){
		NumBytesRead = 0;

		Success = PeekNamedPipe(
			PipeReadHandle,				// handle to pipe to copy from
			PipeData,					// pointer to data buffer
			1,							// size, in bytes, of data buffer
			&NumBytesRead,				// pointer to number of bytes read
			&TotalBytesAvailable,		// pointer to total number of bytes available
			&BytesLeftThisMessage		// pointer to unread bytes in this message
		);

		if ( !Success ){
			ShowLastError("PeekNamedPipe fialed");
			break;
		}

		if ( NumBytesRead ){
			Success = ReadFile(
				PipeReadHandle,			// handle to pipe to copy from
				PipeData,				// address of buffer that receives data
				TotalBytesAvailable,	// number of bytes to read
				&NumBytesRead,			// address of number of bytes read
				NULL					// address of structure for data for overlapped I/O
			);

			if ( !Success ){
				ShowLastError("ReadFile fialed");
				break;
			}

			//	Replace backspaces with spaces.
			for( DWORD ii = 0; ii < NumBytesRead; ii++ ){
				if( PipeData[ii] == '\b' ){
					PipeData[ii] = ' ';
				}
			}

			// Convert utf-8 to unicode
            std::string str = std::string(PipeData,NumBytesRead);
			m_stdout += str;

			//	Append the output to the vector<string>.
			//AppendText(wstr);
		}
		else{
			//	If the child process has completed, break out.
			if ( WaitForSingleObject(ProcessInfo.hProcess, 0) == WAIT_OBJECT_0 )	// lint !e1924 (warning about C-style cast)
				break;

			//	If the user cancelled the operation, terminate the process.
			if ( m_bStopped ){
				Success = TerminateProcess(ProcessInfo.hProcess,0);

				if ( Success )
					AppendText("\r\nCancelled.\r\n\r\nProcess terminated successfully.\r\n");
				else
					ShowLastError("Error terminating process.");

				break;
			}

			//	Sleep.
			::Sleep(m_dwSleepMilliseconds);
		}
	}

	return true;
}

void fuConsoleRunner::AppendText(const std::string & Text)
{
    m_output << Text;

    //std::vector<std::string> vec;
    //Text.Split("\r\n",vec);
    //foreach(std::string & str, vec){
    //    m_cmderr->push_back(str);
    //}
}

void fuConsoleRunner::ShowLastError(const std::string & szText)
{
	LPTSTR		lpMsgBuf;
	DWORD		Success;
	std::string	Msg = szText + "\r\n";

	// Get the system error message.
	Success = ::FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,	// DWORD	dwFlags,
		NULL,															// LPCVOID	lpSource,
		::GetLastError(),												// DWORD	dwMessageId,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),						// DWORD	dwLanguageId,	// lint !e1924 (warning about C-style cast)
		(LPTSTR)&lpMsgBuf,												// LPWSTR	lpBuffer,
		0,																// DWORD	nSize,
		NULL );															// va_list *Arguments

	if ( Success )
		Msg += lpMsgBuf;
	else
		Msg += "No status because FormatMessage failed.\r\n";

	AppendText(Msg);
}

END_NAMESPACE_CPPX

#endif
